#pragma once

#include <AVUIDependencyObject.h>
#include <AVUIRect3D.h>
#include <AVUIIVisual3DContainer.h>
#include <AVUIIAnimatable.h>

namespace AVUI {

class Transform3D;
class GeneralTransform3D;
class GeneralTransform3DTo2D;
class Viewport3DVisual;
class Model3D;
class RayHitTestParameters;
class HitTestParameters3D;
class IVisual3DContainer;
class GeneralTransform3DGroup;

#pragma region Internal Helpers

class HTFBInterpreter
{
public:
    static bool DoHitTest(HitTestFilterBehavior::Enum behavior) { return ((behavior & HitTestFilterBehavior::ContinueSkipChildren) == HitTestFilterBehavior::ContinueSkipChildren); }
    static bool IncludeChildren(HitTestFilterBehavior::Enum behavior) { return ((behavior & HitTestFilterBehavior::ContinueSkipSelf) == HitTestFilterBehavior::ContinueSkipSelf); }
    static bool SkipSubgraph(HitTestFilterBehavior::Enum behavior) { return (behavior == HitTestFilterBehavior::ContinueSkipSelfAndChildren); }
    static bool Stop(HitTestFilterBehavior::Enum behavior) { return ((behavior & HitTestFilterBehavior::Stop) == HitTestFilterBehavior::Stop); }
};

#pragma endregion Internal Helpers


/// <summary>Provides services and properties common to visual 3-D objects, including hit-testing, coordinate transformation, and bounding box calculations.</summary>
class Visual3D : public DependencyObject, public IVisual3DContainer, public IAnimatable
{
public:
    DECLARE_ELEMENT(Visual3D, DependencyObject);

#pragma region Public Methods

    /// <summary>Applies the effect of a given <see cref="T:System.Windows.Media.Animation.AnimationClock" /> to a given dependency property.</summary>
    /// <param name="dp">The <see cref="T:System.Windows.DependencyProperty" /> to animate.</param>
    /// <param name="clock">The <see cref="T:System.Windows.Media.Animation.AnimationClock" /> that animates the property.</param>
    void ApplyAnimationClock(DependencyProperty* pDP, AnimationClock* pClock) { ApplyAnimationClock(pDP, pClock, HandoffBehavior::SnapshotAndReplace); }

    /// <summary>Applies the effect of a given <see cref="T:System.Windows.Media.Animation.AnimationClock" /> to a given dependency property. The effect of the new <see cref="T:System.Windows.Media.Animation.AnimationClock" /> on any current animations is determined by the value of the <paramref name="handoffBehavior" /> parameter.</summary>
    /// <param name="dp">The <see cref="T:System.Windows.DependencyProperty" /> to animate.</param>
    /// <param name="clock">The <see cref="T:System.Windows.Media.Animation.AnimationClock" /> that animates the property.</param>
    /// <param name="handoffBehavior">Determines how the new <see cref="T:System.Windows.Media.Animation.AnimationClock" /> will transition from or affect any current animations on the property.</param>
    void ApplyAnimationClock(DependencyProperty* pDP, AnimationClock* pClock, HandoffBehavior::Enum handoffBehavior);

    /// <summary>Initiates an animation sequence for the <see cref="T:System.Windows.DependencyProperty" /> object, based on the specified <see cref="T:System.Windows.Media.Animation.AnimationTimeline" />.</summary>
    /// <param name="dp">The object to animate.</param>
    /// <param name="animation">The timeline with the necessary functionality to animate the property.</param>
    void BeginAnimation(DependencyProperty* pDP, AnimationTimeline* pAnimation) { BeginAnimation(pDP, pAnimation, HandoffBehavior::SnapshotAndReplace); }

    /// <summary>Initiates an animation sequence for the <see cref="T:System.Windows.DependencyProperty" />.object, based on both the specified <see cref="T:System.Windows.Media.Animation.AnimationTimeline" /> and <see cref="T:System.Windows.Media.Animation.HandoffBehavior" />.</summary>
    /// <param name="dp">The object to animate.</param>
    /// <param name="animation">The timeline with the necessary functionality to tailor the new animation.</param>
    /// <param name="handoffBehavior">The object specifying the manner in which to interact with all relevant animation sequences.</param>
    void BeginAnimation(DependencyProperty* pDP, AnimationTimeline* pAnimation, HandoffBehavior::Enum handoffBehavior);

    /// <summary>Returns the common ancestor of the visual object and another specified visual object.</summary>
    /// <returns>The common ancestor of the current visual object and <paramref name="otherVisual" />, or null if no common ancestor is found.</returns>
    /// <param name="otherVisual">The visual object with which to find a common ancestor.</param>
    DependencyObject* FindCommonVisualAncestor(DependencyObject* pOtherVisual);

    /// <summary>Retrieves the base value of the specified <see cref="T:System.Windows.DependencyProperty" /> object.</summary>
    /// <returns>The object representing the base value of <paramref name="Dp" />.</returns>
    /// <param name="dp">The object for which the base value is being requested.</param>
    Object* GetAnimationBaseValue(DependencyProperty* pDP);

    /// <summary>Determines whether the visual object is an ancestor of the descendant visual object.</summary>
    /// <returns>True if the visual object is an ancestor of <paramref name="descendant" />; otherwise, false.</returns>
    /// <param name="descendant">Visual that is a possible descendant.</param>
    bool IsAncestorOf(DependencyObject* pDescendant);
 
    /// <summary>Determines whether the visual object is a descendant of the ancestor visual object. </summary>
    /// <returns>true if the visual object is a descendant of <paramref name="ancestor" />; otherwise, false.</returns>
    /// <param name="ancestor">Visual that is a possible ancestor.</param>
    bool IsDescendantOf(DependencyObject* pAncestor);

    /// <summary>Returns a transform that can be used to transform coordinates from this <see cref="T:System.Windows.Media.Media3D.Visual3D" /> object to the specified <see cref="T:System.Windows.Media.Media3D.Visual3D" /> ancestor of the object.</summary>
    /// <returns>A <see cref="T:System.Windows.Media.Media3D.GeneralTransform3D" /> object or null, if the transform cannot be created.</returns>
    /// <param name="ancestor">The <see cref="T:System.Windows.Media.Media3D.Visual3D" /> to which the coordinates are transformed.</param>
    /// <exception cref="T:System.ArgumentNullException">
    /// <paramref name="ancestor" /> is null.</exception>
    /// <exception cref="T:System.ArgumentException">The specified <paramref name="ancestor" /> object is not an ancestor of this object.</exception>
    /// <exception cref="T:System.InvalidOperationException">The <see cref="T:System.Windows.Media.Media3D.Visual3D" /> objects are not related.</exception>
    TRefCountedPtr<GeneralTransform3D> TransformToAncestor(Visual3D* pAncestor);

    /// <summary>Returns a transform that can be used to transform coordinates from this <see cref="T:System.Windows.Media.Media3D.Visual3D" /> object to the specified <see cref="T:System.Windows.Media.Visual" /> ancestor of the object.</summary>
    /// <returns>A <see cref="T:System.Windows.Media.Media3D.GeneralTransform3DTo2D" /> object or null, if the transform cannot be created.</returns>
    /// <param name="ancestor">The <see cref="T:System.Windows.Media.Visual" /> to which the coordinates are transformed.</param>
    /// <exception cref="T:System.ArgumentNullException">
    /// <paramref name="ancestor" /> is null.</exception>
    /// <exception cref="T:System.ArgumentException">The specified <paramref name="ancestor" /> object is not an ancestor of this object.</exception>
    TRefCountedPtr<GeneralTransform3DTo2D> TransformToAncestor(Visual* pAncestor);

    /// <summary>Returns a transform that can be used to transform coordinates from this <see cref="T:System.Windows.Media.Media3D.Visual3D" /> object to the specified <see cref="T:System.Windows.Media.Media3D.Visual3D" /> descent object.</summary>
    /// <returns>A <see cref="T:System.Windows.Media.Media3D.GeneralTransform3D" /> object or null, if the transform from <paramref name="descendant" /> to this object is non-invertible.</returns>
    /// <param name="descendant">The <see cref="T:System.Windows.Media.Media3D.Visual3D" /> to which the coordinates are transformed.</param>
    /// <exception cref="T:System.ArgumentNullException">
    /// <paramref name="descendant" /> is null.</exception>
    /// <exception cref="T:System.ArgumentException">This object is not an ancestor of the specified <paramref name="descendant" /> object.</exception>
    /// <exception cref="T:System.InvalidOperationException">The <see cref="T:System.Windows.Media.Media3D.Visual3D" /> objects are not related.</exception>
    TRefCountedPtr<GeneralTransform3D> TransformToDescendant(Visual3D* pDescendant);

#pragma endregion Public Methods
 
#pragma region Public Properties

    /// <summary>Gets a value indicating whether this <see cref="T:System.Windows.Media.Media3D.Visual3D" /> has any animated properties.</summary>
    /// <returns>true if this element has animations; otherwise, false.</returns>
    bool get_HasAnimatedProperties() { VerifyAccess(); return get_IAnimatable_HasAnimatedProperties(); }

    /// <summary>Gets or sets the transformation that is applied to the 3-D object.</summary>
    /// <returns>The transformation to apply to the 3-D object. The default is the <see cref="P:System.Windows.Media.Media3D.Transform3D.Identity" /> transformation.</returns>
    TRefCountedPtr<Transform3D> get_Transform() { return (Transform3D*)GetValue(get_TransformProperty()).get_Ptr(); }
    void set_Transform(Transform3D* pValue) { SetValue(get_TransformProperty(), (Object*)pValue); } 
    DECLARE_DEPENDENCY_PROPERTY(Transform);

#pragma endregion Public Properties

protected:

#pragma region Protected Methods

    /// <summary>Defines the parent-child relationship between two 3-D visuals.</summary>
    /// <param name="child">The child 3-D visual object to add to parent 3-D visual.</param>
    /// <exception cref="T:System.InvalidOperationException">The children collection cannot be modified when a visual children iteration is in progress.</exception>
    void AddVisual3DChild(Visual3D* pChild);

    /// <summary>Returns the specified <see cref="T:System.Windows.Media.Media3D.Visual3D" /> in the parent <see cref="T:System.Windows.Media.Media3D.Visual3DCollection" />.</summary>
    /// <returns>The child in the collection at the specified <paramref name="index" /> value.</returns>
    /// <param name="index">The index of the 3-D visual object in the collection.</param>
    /// <exception cref="T:System.ArgumentOutOfRangeException">The <paramref name="index" /> value is invalid.</exception>
    virtual Visual3D* GetVisual3DChild(int index);

    /// <summary>Called when the <see cref="T:System.Windows.Media.Media3D.Visual3DCollection" /> of the visual object is modified.</summary>
    /// <param name="visualAdded">The <see cref="T:System.Windows.Media.Media3D.Visual3D" /> that was added to the collection.</param>
    /// <param name="visualRemoved">The <see cref="T:System.Windows.Media.Media3D.Visual3D" /> that was removed from the collection.</param>
    virtual void OnVisualChildrenChanged(DependencyObject* pVisualAdded, DependencyObject* pVisualRemoved) { }

    /// <summary>Called when the parent of the 3-D visual object is changed.</summary>
    /// <param name="oldParent">A value of type <see cref="T:System.Windows.DependencyObject" /> that represents the previous parent of the <see cref="T:System.Windows.Media.Media3D.Visual3D" /> object. If the <see cref="T:System.Windows.Media.Media3D.Visual3D" /> object did not have a previous parent, the value of the parameter is null.</param>
    virtual void OnVisualParentChanged(DependencyObject* pOldParent) { };

    /// <summary>Removes the parent-child relationship between two 3-D visuals.</summary>
    /// <param name="child">The child 3-D visual object to remove from the parent visual.</param>
    void RemoveVisual3DChild(Visual3D* pChild);

#pragma endregion Protected Methods

#pragma region Protected Methods

    /// <summary>Gets the number of child elements for the <see cref="T:System.Windows.Media.Media3D.Visual3D" /> object.</summary>
    /// <returns>The number of child elements.</returns>
    virtual int get_Visual3DChildrenCount() { return 0; }

    /// <summary>Gets or sets the <see cref="T:System.Windows.Media.Media3D.Model3D" /> object to render.</summary>
    /// <returns>The <see cref="T:System.Windows.Media.Media3D.Model3D" /> object to render.</returns>
    Model3D* get_Visual3DModel();
    void set_Visual3DModel(Model3D* pValue);

#pragma endregion Protected Methods

public:

    Visual3D();

#pragma region Internal Methods

    virtual void AddInheritanceContext(DependencyObject* pContext, DependencyProperty* pProperty) AVUI_OVERRIDE;
    Rect3D CalculateSubgraphBoundsInnerSpace() { return get_BBoxSubgraph(); }
    Rect3D CalculateSubgraphBoundsOuterSpace();
    bool CheckFlagsAnd(VisualFlags::Enum flags) { return  (m_flags & flags) == flags; }
    bool CheckFlagsOr(VisualFlags::Enum flags);
    static bool DoAnyChildrenHaveABitSet(Visual3D* pVisual, VisualFlags::Enum flag);
    bool DoesRayHitSubgraphBounds(RayHitTestParameters* pRayParams);
    virtual void EvaluateAnimatedValueCore(DependencyProperty* pDP, PropertyMetadata* pMetadata, EffectiveValueEntry* pEntry) AVUI_OVERRIDE;
    DependencyObject* FindFirstAncestorWithFlagsAnd(VisualFlags::Enum flag);
    virtual void FireOnVisualParentChanged(DependencyObject* pOldParent);
    Rect3D GetContentBounds();
    void HitTest(HitTestFilterCallback* pFilterCallback, HitTestResultCallback* pResultCallback, HitTestParameters3D* pHitTestParameters);
    HitTestResultBehavior::Enum HitTestChildren(HitTestFilterCallback* pFilterCallback, RayHitTestParameters* pRayParams) { return HitTestChildren(pFilterCallback, pRayParams, (IVisual3DContainer*)this); }
    static HitTestResultBehavior::Enum HitTestChildren(HitTestFilterCallback* pFilterCallback, RayHitTestParameters* pRayParams, IVisual3DContainer* pContainer);
    virtual DependencyObject* InternalGet2DOr3DVisualChild(int index) { return GetVisual3DChild(index); }
    TRefCountedPtr<GeneralTransform3DTo2D> InternalTransformToAncestor(Visual* pAncestor);
    virtual void OnInheritanceContextChangedCore(EventArgs* pArgs) AVUI_OVERRIDE;
    bool PrecomputeContent();
    void PrecomputeRecursive(Rect3D* pBboxSubgraph);
    static void ProcessAncestorChangedNotificationRecursive(DependencyObject* e, AncestorChangedEventArgs* pArgs);
    static void PropagateFlags(Visual3D* pE, VisualFlags::Enum flags);
    HitTestResultBehavior::Enum RayHitTest(HitTestFilterCallback* pFilterCallback, RayHitTestParameters* pRayParams);
    void RayHitTestInternal(HitTestFilterCallback* pFilterCallback, RayHitTestParameters* pRayParams);
    virtual void RemoveInheritanceContext(DependencyObject* pContext, DependencyProperty* pProperty) AVUI_OVERRIDE;
    void RenderChanged(Object* pSender, EventArgs* e);
    void SetFlags(bool value, VisualFlags::Enum Flags) { m_flags = value ? (m_flags | Flags) : (m_flags & ~Flags); }
    void SetFlagsToRoot(bool value, VisualFlags::Enum flag);
    void SetParent(Visual3D* pNewParent);
    void SetParent(Visual* pNewParent);
    void VerifyAPIReadOnly();
    void VerifyAPIReadOnly(DependencyObject* pOther);
    void VerifyAPIReadWrite();
    void VerifyAPIReadWrite(DependencyObject* pOther);

#pragma endregion Internal Methods

#pragma region Internal Properties

    Rect3D get_BBoxSubgraph();
    virtual bool get_HasMultipleInheritanceContexts() AVUI_OVERRIDE { return DependencyObject::get_HasMultipleInheritanceContexts(); }
    virtual DependencyObject* get_InheritanceContext() AVUI_OVERRIDE;

    bool get_InternalIsVisible();
    void set_InternalIsVisible(bool value);

    virtual int get_InternalVisual2DOr3DChildrenCount() { return get_Visual3DChildrenCount(); }
    DependencyObject* get_InternalVisualParent();

    bool get_IsVisualChildrenIterationInProgress() { return CheckFlagsAnd(VisualFlags::IsVisualChildrenIterationInProgress); }
    void set_IsVisualChildrenIterationInProgress(bool value) { SetFlags(value, VisualFlags::IsVisualChildrenIterationInProgress); }

    int get_ParentIndex() { return m_parentIndex; }
    void set_ParentIndex(int parentIndex) { m_parentIndex = parentIndex; }

    Rect get_Visual2DContentBounds();
    Rect3D get_VisualContentBounds();

    Rect3D get_VisualDescendantBounds();
    
#pragma endregion Internal Properties

#pragma region Internal Events

    IMPLEMENT_EVENT(VisualAncestorChanged, AncestorChangedEventArgs); 
 
#pragma endregion Internal Events

#pragma region IVisual3DContainer Methods

    virtual void AddChild(Visual3D* pChild) { AddVisual3DChild(pChild); }
    virtual Visual3D* GetChild(int index) { return GetVisual3DChild(index); }
    virtual int GetChildrenCount() { return get_Visual3DChildrenCount(); }
    virtual void RemoveChild(Visual3D* pChild) { return RemoveVisual3DChild(pChild); }

#pragma endregion IVisual3DContainer Methods

#pragma region WPFG / Deviation

    virtual void Render3D(Renderer* pRenderer, const Matrix3D& parentMatrix, const Rect& clipRect);
    virtual void RenderDependencies3D(Renderer* pRenderer, const Matrix3D& parentMatrix, const Rect& clipRect);

#pragma endregion WPFG / Deviation

private:

#pragma region Private Methods
    static void StaticConstructor();

    void AddOrRemoveInheritanceContext(DependencyObject* pNewInheritanceContext);
    TRefCountedPtr<GeneralTransform3D> InternalTransformToAncestor(Visual3D* pAncestor, bool inverse);
    void SetInheritanceContext(DependencyObject* pNewInheritanceContext);
    static void TransformPropertyChanged(DependencyObject* pDO, DependencyPropertyChangedEventArgs* e);
    void Visual3DModelPropertyChanged(Object* pO, EventArgs* e);
    void Visual3DModelPropertyChanged(Model3D* pOldValue, bool isSubpropertyChange);

    DependencyObject* get_UseParentAsContext() { static DependencyObject useParentAsContext; return &useParentAsContext; }

#pragma endregion Private Methods

#pragma region Private Fields

    static UncommonField<Object> m_2DParent;
    TWeakPtr<Visual3D> m_p3DParent;
    Rect3D m_bboxContent;
    Rect3D m_bboxSubgraph;
    unsigned int m_flags;
    static UncommonField<Object> m_inheritanceContext;
    bool m_internalIsVisible;
    int m_parentIndex;
    TRefCountedPtr<Model3D> m_pVisual3DModel;

    // AVUI elements -- Used to cache/clip early
    Rect m_bboxViewport;
#pragma endregion Private Fields

 


};


}; // namespace AVUI
